package com.mingqijia.gassafety.authority.vcc.filter;

import cn.hutool.json.JSONObject;
import com.gap.basic.base.CommonResponse;
import com.gap.cache.redis.RedisCache;
import com.mingqijia.gassafety.authority.dal.helper.ConfigureHolder;
import com.mingqijia.gassafety.authority.domain.adapter.BenAdapter;
import com.mingqijia.gassafety.authority.vcc.provider.VccOauthProvider;
import com.mingqijia.gassafety.authority.vcc.user.VccUserLoginHolder;
import com.mingqijia.gassafety.authority.vcc.valueobject.VccUserVO;
import com.mingqijia.gassafety.shared.constant.Constants;
import com.mingqijia.gassafety.shared.handler.GlobalAuthenticationException;
import com.mingqijia.gassafety.shared.utils.ApplicationUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Instant;
import java.util.Objects;


/**
 * token校验,引用的stackoverflow一个答案里的处理方式
 * @author Damon S.
 * @version v1.0.1
 * @date 2020年07月25日 12:00
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class VccAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.header}")
    private String tokenHeader;

    private final VccOauthProvider provider;
    private final RedisCache cacheManager;
    private final VccUserLoginHolder holder;
    private final BenAdapter benAdapter;
    private final ConfigureHolder cHolder;

    private static final int FAILURE = 10000;
    private static final String TIP_RELOGIN = "登录已失效，请重新登录";
    private static final String TIP_USER_FORBID = "账户已禁用";

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String tokenValue = request.getHeader(tokenHeader);
        if (ObjectUtils.isEmpty(tokenValue) || !tokenValue.startsWith(Constants.PREFIX_BEARER)
                || request.getRequestURI().contains(Constants.TOKEN_URL)
                || request.getRequestURI().contains(Constants.IMEI_CHECK_URL)
                || request.getRequestURI().contains(Constants.SYNC_ORDER_CHECK_URL)) {
            filterChain.doFilter(request, response);
            return;
        }

        String pureToken = tokenValue.replace(Constants.PREFIX_BEARER, Constants.STR_EMPTY);
        VccUserVO backend = cacheManager.getCacheObject(
                String.format(Constants.KEY_MANAGE_PROFILE, pureToken)
        );
        JSONObject tokenObject = null;
        // 使用token从缓存中查询为空，则为CODE模式
        if (Objects.isNull(backend)) {
            VccUserVO userVO = cacheManager.getCacheObject(String.format(Constants.KEY_MANAGE_PROFILE_CODE, pureToken));
            log.info("code模式获取到本地缓存中对应的用户信息，VccUserVO ：{}", userVO);
            if(Objects.isNull(userVO)){
                log.error("本地缓存token过期，token={}", tokenValue);
                ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_RELOGIN));
                return;
            }
            request.setAttribute(Constants.HEADER_SP_ID, userVO.getWorkingSpId());

//            VccUserVO checkToken;
//            try {
//                checkToken = provider.checkToken(tokenValue);
//            } catch (Exception e) {
//                // token 失效时间配置
//                String tokenRefreshEnabled = cHolder.getConfigByKey(
//                        Constants.CACHE_KEY_TOKEN_REFRESH
//                );
//                if (!Constants.STR_ON.equals(tokenRefreshEnabled)) {
//                    log.error("验证燃气token失败1，token={}", tokenValue);
//                    ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_RELOGIN));
//                    return;
//                }
//                checkToken = null;
//                log.error("验证燃气token失败，将重新获取[失败的token={}]", tokenValue);
//            }
//            String refreshToken = ObjectUtils.isEmpty(checkToken)
//                    ? cacheManager.getCacheObject(String.format(Constants.KEY_MANAGE_REFRESH_TOKEN, pureToken))
//                    : checkToken.getRefreshToken();

            String refreshToken = cacheManager.getCacheObject(String.format(Constants.KEY_MANAGE_REFRESH_TOKEN, pureToken));

            if (StringUtils.isEmpty(refreshToken)) {
                log.error("验证燃气token失败2，token={}", tokenValue);
                ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_RELOGIN));
                return;
            }
            if (StringUtils.isEmpty(refreshToken)) {
                ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, "token不合法"));
                return;
            }
            backend = cacheManager.getCacheObject(String.format(Constants.KEY_MANAGE_PROFILE, refreshToken));
            // 应西南舒适家需求，暂时实现方案：登录态过期后自动刷新，保持登录态一直在线
            if (ObjectUtils.isEmpty(backend) || (backend.getExpireAt() <= Instant.now().getEpochSecond())) {
                // 登录台已失效，重新获取token
                log.info("当前token已失效，用于重新获取token的refreshToken=[{}]", refreshToken);
                tokenObject = new JSONObject();
                backend = refreshTokenByCodeLoginType(refreshToken, tokenObject);
            }
        } else {
            // 非CODE模式的登录认证方式需要考虑token过期
            // 五分钟前重新 获取token
            if (ObjectUtils.isEmpty(backend) || (backend.getExpireAt() <= Instant.now().getEpochSecond())) {
                log.error("token过期，user={}, token={}", backend.getUserId(), tokenValue);
                ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_RELOGIN));
                return;
            }
        }
        if (ObjectUtils.isEmpty(backend)) {
            log.error("验证燃气token失败2，token={}", tokenValue);
            ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_RELOGIN));
            return;
        }
        if (!backend.isEnabled()) {
            ApplicationUtils.writeOutJson(response, CommonResponse.failure(FAILURE, TIP_USER_FORBID));
            return;
        }

        // 将用户对象设置为principle
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                backend, tokenObject, backend.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        filterChain.doFilter(request, response);
    }

    private VccUserVO refreshTokenByCodeLoginType(String refreshToken, JSONObject jsonObject) {
        VccUserVO refreshed = null;
        try {
            refreshed = provider.refreshToken(refreshToken);
        } catch (GlobalAuthenticationException e) {
            // 如果燃气云返回的是 授权失败
            if (Constants.CODE_GAS_AUTH_FAILED == e.getCode()) {
                return null;
            }
        }
        // 重新构建登录用户
        VccUserVO backend = new VccUserVO();
        backend.setOpenid(refreshed.getOpenid());
        backend.setLoginType(Constants.STR_CODE);
        backend.setExpireTime(refreshed.getExpireTime());
        backend.setExpireAt(Instant.now().getEpochSecond() + refreshed.getExpireTime());
        backend.setRefreshToken(refreshed.getRefreshToken());
        backend.setRefreshTokenExpiration(refreshed.getRefreshTokenExpiration());
        backend.setToken(refreshed.getToken());
        // 填充登录用户信息，并更新缓存信息
        holder.buildVccUserDetail(backend);
        holder.whenManagerLogin(backend);

        jsonObject.putOpt("token", backend.getToken());
        benAdapter.refreshBenToken(backend.getUsername());
        log.info("refresh登录用户信息:[{}]", backend);

        return backend;
    }
}
